package mirrormonkey.util.annotations.parsing.classlevel;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

import junit.framework.Assert;
import mirrormonkey.util.annotations.control.AnnotationOverride;
import mirrormonkey.util.annotations.control.IRClass;
import mirrormonkey.util.annotations.parsing.AnnotationParser;
import mirrormonkey.util.annotations.parsing.ClassIR;

import org.junit.Test;

/**
 * Contains test cases concerning management and annotation collecting of
 * memberless classes that form hierarchies.
 * 
 * @author Philipp Christian Loewner
 * 
 */
public class HierarchicalMemberlessClassIRTest {

	/**
	 * Some empty annotation to use for testing
	 */
	@Retention(RetentionPolicy.RUNTIME)
	public static @interface CustomAnnotation {
	}

	/**
	 * Another empty annotation to use for testing
	 */
	@Retention(RetentionPolicy.RUNTIME)
	public static @interface CustomAnnotation2 {
	}

	/**
	 * Another empty annotation to use for testing
	 */
	@Retention(RetentionPolicy.RUNTIME)
	public static @interface CustomAnnotation3 {
	}

	/**
	 * Common <tt>AnnotationParser</tt> for test cases to use
	 */
	public static AnnotationParser parser = new AnnotationParser();

	/**
	 * Tests if an annotation is collected from an extending class.
	 */
	@Test
	public void testCollectFromClassHierarchy() {
		@IRClass(startCollect = { CustomAnnotation.class })
		class First {
		}
		@CustomAnnotation
		class Second extends First {
		}
		Assert.assertNotNull(parser.parseClass(Second.class, null)
				.getCollectedAnnotation(CustomAnnotation.class));
	}

	/**
	 * Tests if multiple annotations are collected from multiple extending
	 * classes.
	 */
	@Test
	public void testCollectMultipleFromClassHierarchy() {
		@IRClass(startCollect = { CustomAnnotation.class,
				CustomAnnotation2.class, CustomAnnotation3.class })
		@CustomAnnotation
		class First {
		}
		@CustomAnnotation2
		class Second extends First {
		}
		@CustomAnnotation3
		class Third extends Second {
		}
		ClassIR ir = parser.parseClass(Third.class, null);
		Assert.assertNotNull(ir.getCollectedAnnotation(CustomAnnotation.class));
		Assert.assertNotNull(ir.getCollectedAnnotation(CustomAnnotation2.class));
		Assert.assertNotNull(ir.getCollectedAnnotation(CustomAnnotation3.class));

	}

	/**
	 * Tests if the collection declarations of multiple annotations on the
	 * hierarchy cumulate.
	 */
	@Test
	public void testCumulatingCollectionOrder() {
		@IRClass(startCollect = { CustomAnnotation.class })
		class First {
		}
		@IRClass(startCollect = { CustomAnnotation2.class })
		class Second extends First {
		}
		@CustomAnnotation
		@CustomAnnotation2
		@CustomAnnotation3
		@IRClass(startCollect = { CustomAnnotation3.class })
		class Third extends Second {
		}
		ClassIR ir = parser.parseClass(Third.class, null);
		Assert.assertNotNull(ir.getCollectedAnnotation(CustomAnnotation.class));
		Assert.assertNotNull(ir.getCollectedAnnotation(CustomAnnotation2.class));
		Assert.assertNotNull(ir.getCollectedAnnotation(CustomAnnotation3.class));
	}

	/**
	 * Tests if collection declarations have no effect on the classes upwards in
	 * the hierarchy.
	 */
	@Test
	public void testCollectionOrderNotApplyBackwards() {
		@CustomAnnotation
		class First {
		}
		@IRClass(startCollect = { CustomAnnotation.class })
		class Second extends First {
		}
		Assert.assertNull(parser.parseClass(Second.class, null)
				.getCollectedAnnotation(CustomAnnotation.class));
	}

	/**
	 * Tests if annotations further down the hierarchy are not collected if the
	 * parsed class has predecessors.
	 */
	@Test
	public void testPartialHierarchyParse() {
		@IRClass(startCollect = { CustomAnnotation.class })
		class First {
		}
		@CustomAnnotation
		@SuppressWarnings("unused")
		class Second extends First {
		}
		Assert.assertNull(parser.parseClass(First.class, null)
				.getCollectedAnnotation(CustomAnnotation.class));
	}

	/**
	 * Tests if annotations are no longer collected further down the hierarchy
	 * if they are not supposed to be collected any more.
	 */
	@Test
	public void testRejectAnnotation() {
		@IRClass(startCollect = CustomAnnotation.class)
		class First {
		}
		@CustomAnnotation
		@IRClass(stopCollect = CustomAnnotation.class)
		class Second extends First {
		}
		Assert.assertNull(parser.parseClass(Second.class, null)
				.getCollectedAnnotation(CustomAnnotation.class));
	}

	/**
	 * Tests if already collected annotations are removed if so declared with an
	 * <tt>IRClass</tt>.
	 */
	@Test
	public void testRemoveAnnotation() {
		@CustomAnnotation
		@IRClass(startCollect = CustomAnnotation.class)
		class First {
		}
		@IRClass(removeCompletely = CustomAnnotation.class)
		class Second extends First {
		}
		Assert.assertNull(parser.parseClass(Second.class, null)
				.getCollectedAnnotation(CustomAnnotation.class));
	}

	/**
	 * Tests if annotations are no longer collected after an
	 * <tt>AnnotationOverride</tt>.
	 */
	@Test
	public void testRejectAfterOverride() {
		@IRClass(startCollect = CustomAnnotation.class)
		class First {
		}
		@CustomAnnotation
		@AnnotationOverride
		class Second extends First {
		}
		Assert.assertNull(parser.parseClass(Second.class, null)
				.getCollectedAnnotation(CustomAnnotation.class));
	}

	/**
	 * Tests if already collected annotations are removed after an
	 * <tt>AnnotationOverride</tt>.
	 */
	@Test
	public void testRemoveAfterOverride() {
		@CustomAnnotation
		@IRClass(startCollect = CustomAnnotation.class)
		class First {
		}
		@AnnotationOverride
		class Second extends First {
		}
		Assert.assertNull(parser.parseClass(Second.class, null)
				.getCollectedAnnotation(CustomAnnotation.class));
	}

	/**
	 * This is a valid IR class that can be used for classes. Since it declares
	 * constructors for both <tt>AnnotationParser</tt> and <tt>ClassIR</tt>, it
	 * will be created by the parser if it is either the first declared IR for a
	 * class or the previous IR was a <tt>ClassIR</tt>.<br>
	 */
	public static class ExtendingClassIR extends ClassIR {
		public ExtendingClassIR(AnnotationParser parser, Class<?> forClass) {
			super(parser, forClass);
		}

		public ExtendingClassIR(ClassIR previous, Class<?> forClass) {
			super(previous, forClass);
		}
	}

	/**
	 * Tests if it is possible to change a previously declared IR class using
	 * <tt>IRClass</tt>.
	 */
	@Test
	public void testChangeIRClass() {
		@IRClass(value = ClassIR.class)
		class First {
		}
		@IRClass(value = ExtendingClassIR.class)
		class Second extends First {
		}
		Assert.assertEquals(ExtendingClassIR.class,
				parser.parseClass(Second.class, null).getClass());
	}

	/**
	 * Tests if changing the IR class will keep collecting the same annotation
	 * classes.
	 */
	@Test
	public void testChangeKeepsCollecting() {
		@IRClass(value = ClassIR.class, startCollect = CustomAnnotation.class)
		class First {
		}
		@IRClass(value = ExtendingClassIR.class)
		class Second extends First {
		}
		@CustomAnnotation
		class Third extends Second {
		}
		Assert.assertNotNull(parser.parseClass(Third.class, null)
				.getCollectedAnnotation(CustomAnnotation.class));
	}

	/**
	 * Tests if changing the IR class will keep the previously collected
	 * annotations if not stated otherwise.
	 */
	@Test
	public void testChangeKeepsCollected() {
		@CustomAnnotation
		@IRClass(value = ClassIR.class, startCollect = CustomAnnotation.class)
		class First {
		}
		@IRClass(value = ExtendingClassIR.class)
		class Second extends First {
		}
		Assert.assertNotNull(parser.parseClass(Second.class, null)
				.getCollectedAnnotation(CustomAnnotation.class));
	}

	/**
	 * Can be used as default <tt>ClassIR</tt>, but will provoke an exception
	 * when used if there is already an IR present that is not an instance of
	 * exactly this class because it is missing a constructor that takes a
	 * <tt>ClassIR</tt> as parameter.
	 */
	public static class InvalidExtendingIR extends ClassIR {
		public InvalidExtendingIR(InvalidExtendingIR previous, Class<?> forClass) {
			super(previous, forClass);
		}

	}

	/**
	 * Tests if IR classes are rejected if they do not provide a constructor
	 * matching the current IR class.
	 */
	@Test(expected = RuntimeException.class)
	public void testRejectIllegalChangeIRClass() {
		@IRClass(value = ClassIR.class)
		class First {
		}
		@IRClass(value = InvalidExtendingIR.class)
		class Second extends First {
		}
		parser.parseClass(Second.class, null);
	}
}
